//! Utils that need libc.
#![allow(dead_code)]

use std::{fmt, io};

/// Handles the usual libc function that returns `-1` to indicate an error.
#[track_caller]
pub fn errno_result<T: From<i8> + Ord>(ret: T) -> io::Result<T> {
    use std::cmp::Ordering;
    match ret.cmp(&(-1i8).into()) {
        Ordering::Equal => Err(io::Error::last_os_error()),
        Ordering::Greater => Ok(ret),
        Ordering::Less => panic!("unexpected return value: less than -1"),
    }
}
/// Check that a function with errno error handling succeeded (i.e., returned 0).
#[track_caller]
pub fn errno_check<T: From<i8> + Ord + fmt::Debug>(ret: T) {
    assert_eq!(errno_result(ret).unwrap(), 0i8.into(), "wrong successful result");
}

pub unsafe fn read_all(
    fd: libc::c_int,
    buf: *mut libc::c_void,
    count: libc::size_t,
) -> libc::ssize_t {
    assert!(count > 0);
    let mut read_so_far = 0;
    while read_so_far < count {
        let res = libc::read(fd, buf.add(read_so_far), count - read_so_far);
        if res < 0 {
            return res;
        }
        if res == 0 {
            // EOF
            break;
        }
        read_so_far += res as libc::size_t;
    }
    return read_so_far as libc::ssize_t;
}

/// Read exactly `N` bytes from `fd`. Error if that many bytes could not be read.
#[track_caller]
pub fn read_all_into_array<const N: usize>(fd: libc::c_int) -> Result<[u8; N], libc::ssize_t> {
    let mut buf = [0; N];
    let res = unsafe { read_all(fd, buf.as_mut_ptr().cast(), buf.len()) };
    if res >= 0 {
        assert_eq!(res as usize, buf.len());
        Ok(buf)
    } else {
        Err(res)
    }
}

pub unsafe fn write_all(
    fd: libc::c_int,
    buf: *const libc::c_void,
    count: libc::size_t,
) -> libc::ssize_t {
    assert!(count > 0);
    let mut written_so_far = 0;
    while written_so_far < count {
        let res = libc::write(fd, buf.add(written_so_far), count - written_so_far);
        if res < 0 {
            return res;
        }
        // Apparently a return value of 0 is just a short write, nothing special (unlike reads).
        written_so_far += res as libc::size_t;
    }
    return written_so_far as libc::ssize_t;
}

/// Write the entire `buf` to `fd`. Error if not all bytes could be written.
#[track_caller]
pub fn write_all_from_slice(fd: libc::c_int, buf: &[u8]) -> Result<(), libc::ssize_t> {
    let res = unsafe { write_all(fd, buf.as_ptr().cast(), buf.len()) };
    if res >= 0 {
        assert_eq!(res as usize, buf.len());
        Ok(())
    } else {
        Err(res)
    }
}

#[cfg(any(target_os = "linux", target_os = "android", target_os = "illumos"))]
#[allow(unused_imports)]
pub mod epoll {
    use libc::c_int;
    pub use libc::{EPOLL_CTL_ADD, EPOLL_CTL_DEL, EPOLL_CTL_MOD};
    // Re-export some constants we need a lot for this.
    pub use libc::{EPOLLET, EPOLLHUP, EPOLLIN, EPOLLOUT, EPOLLRDHUP};

    use super::*;

    /// The libc epoll_event type doesn't fit to the EPOLLIN etc constants, so we have our
    /// own type. We also make the data field an int since we typically want to store FDs there.
    #[derive(PartialEq, Debug)]
    pub struct Ev {
        pub events: c_int,
        pub data: c_int,
    }

    #[track_caller]
    pub fn epoll_ctl(epfd: c_int, op: c_int, fd: c_int, event: Ev) -> io::Result<()> {
        let mut event = libc::epoll_event {
            events: event.events.cast_unsigned(),
            u64: event.data.try_into().unwrap(),
        };
        let ret = errno_result(unsafe { libc::epoll_ctl(epfd, op, fd, &raw mut event) })?;
        assert_eq!(ret, 0);
        Ok(())
    }

    /// Helper for the common case of adding an FD to an epoll with the FD itself being
    /// the `data`.
    #[track_caller]
    pub fn epoll_ctl_add(epfd: c_int, fd: c_int, events: c_int) -> io::Result<()> {
        epoll_ctl(epfd, EPOLL_CTL_ADD, fd, Ev { events, data: fd })
    }

    #[track_caller]
    pub fn check_epoll_wait_noblock<const N: usize>(epfd: i32, expected: &[Ev]) {
        let mut array: [libc::epoll_event; N] = [libc::epoll_event { events: 0, u64: 0 }; N];
        let num = errno_result(unsafe {
            libc::epoll_wait(epfd, array.as_mut_ptr(), N.try_into().unwrap(), 0)
        })
        .expect("epoll_wait returned an error");
        let got = &mut array[..num.try_into().unwrap()];
        let got = got
            .iter()
            .map(|e| Ev { events: e.events.cast_signed(), data: e.u64.try_into().unwrap() })
            .collect::<Vec<_>>();
        assert_eq!(got, expected, "got wrong notifications");
    }
}
